package com.lhkj.ct.base.utils;

import cn.hutool.core.util.NumberUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import com.googlecode.aviator.Options;
import com.googlecode.aviator.runtime.FunctionArgument;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.*;
import com.googlecode.aviator.utils.Reflector;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 规则引擎工具类
 */
public class AviatorUtil {

    private final static Logger logger = LoggerFactory.getLogger(AviatorUtil.class);

    // 瑞文智力测验SPM，测验分数与百分比换算
    private final static Map<Double[], List<AgeToExpression>> MAP = new HashMap<>();

    public static Object compute(Map<String, Object> params, String expression) {
        logger.info("公式： {}", expression);
        logger.info("参数： {}", params);
        if (!AviatorEvaluator.containsFunction("IF")) {
            AviatorEvaluator.addFunction(new Function_IF());
        }
        if (!AviatorEvaluator.containsFunction("between")) {
            AviatorEvaluator.addFunction(new MathBetweenFunction());
        }
        if (!AviatorEvaluator.containsFunction("differ")) {
            AviatorEvaluator.addFunction(new ArrayDifferInNumFunction());
        }
        if (!AviatorEvaluator.containsFunction("hld")) {
            AviatorEvaluator.addFunction(new HLDFunction());
        }
        if (!AviatorEvaluator.containsFunction("spm")) {
            AviatorEvaluator.addFunction(new SPMFunction());
        }
        AviatorEvaluator.setOption(Options.CAPTURE_FUNCTION_ARGS, true);
        Expression compiledExp = AviatorEvaluator.compile(expression, true);
        return compiledExp.execute(params);
    }

    /**
     * <p>
     * Aviator 自定义函数`IF`
     * </p>
     */
    static class Function_IF extends AbstractFunction {

        /**
         * IF函数实现逻辑处理
         *
         * @param env:  当前执行的上下文
         * @param arg1: if函数中的判断逻辑
         * @param arg2: arg1为true时的结果
         * @param arg3: arg1为false时的结果
         * @return: com.googlecode.aviator.runtime.type.AviatorObject
         */
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3) {
            boolean ifResult = FunctionUtils.getBooleanValue(arg1, env);
            Number ifTrue = FunctionUtils.getNumberValue(arg2, env);
            Number ifFalse = FunctionUtils.getNumberValue(arg3, env);
            if (ifResult) {
                return new AviatorDouble(ifTrue.doubleValue());
            } else {
                return new AviatorDouble(ifFalse.doubleValue());
            }
        }

        /**
         * 返回方法名
         */
        @Override
        public String getName() {
            return "IF";
        }

    }

    /**
     * <p>
     * Aviator 自定义函数`between`
     * </p>
     */
    static class MathBetweenFunction extends AbstractFunction {

        /**
         * IF函数实现逻辑处理
         *
         * @param env:  当前执行的上下文
         * @param arg1: if函数中的判断逻辑
         * @param arg2: arg1为true时的结果
         * @param arg3: arg1为false时的结果
         * @return: com.googlecode.aviator.runtime.type.AviatorObject
         */
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3) {
            double number = FunctionUtils.getNumberValue(arg1, env).doubleValue();
            Number min = FunctionUtils.getNumberValue(arg2, env);
            Number max = FunctionUtils.getNumberValue(arg3, env);
            return AviatorBoolean.valueOf((min.doubleValue() <= number && number <= max.doubleValue()));
        }

        /**
         * 返回方法名
         */
        @Override
        public String getName() {
            return "between";
        }

    }

    /**
     * <p>
     * Aviator 自定义函数`differ`
     * </p>
     */
    static class ArrayDifferInNumFunction extends AbstractVariadicFunction {

        private static final long serialVersionUID = 2012324454253944383L;

        public String getName() {
            return "differ";
        }

        public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
            if (args != null && args.length >= 3) {
                AviatorObject arg1 = args[0];
                if (arg1 != null && (arg1.getAviatorType() == AviatorType.Double || arg1.getAviatorType() == AviatorType.Long)) {
                    try {
                        // 获取参数第一位，数组差值
                        double difference = FunctionUtils.getNumberValue(arg1, env).doubleValue();
                        double[] numbers = new double[args.length - 1];
                        for (int i = 1; i < args.length; i++) {
                            numbers[i-1] = FunctionUtils.getNumberValue(args[i], env).doubleValue();
                        }
                        Arrays.sort(numbers);
                        for (int i = 0; i < numbers.length; i++) {
                            for (int j = numbers.length - 1; j > i; j--) {
                                if (Math.abs(numbers[i] - numbers[j]) >= difference){
                                    return AviatorBoolean.valueOf(false);
                                }
                            }
                        }
                        return AviatorBoolean.valueOf(true);
                    } catch (Throwable var8) {
                        throw Reflector.sneakyThrow(var8);
                    }
                } else {
                    throw new IllegalArgumentException("Invalid difference:" + (arg1 == null ? "null" : arg1.desc(env)));
                }
            } else {
                throw new IllegalArgumentException("The length of arguments must greater than 2");
            }
        }
    }

    static class HLDFunction extends AbstractVariadicFunction {

        private static final long serialVersionUID = 2012324454253942223L;

        public String getName() {
            return "hld";
        }

        public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
            List<FunctionArgument> args1 = FunctionUtils.getFunctionArguments(env);
            Map<String, Number> map = new LinkedHashMap<>();
            for (int i = 0; i < args1.size(); i++) {
                map.put(args1.get(i).getExpression(), (Number) args[i].getValue(env));
            }
            // 利用Map的entrySet方法，转化为list进行排序
            List<Map.Entry<String, Number>> entryList = new ArrayList<>(map.entrySet());
            // 根据分数倒序
            entryList.sort((o1, o2) -> {
                //正序排列，倒序反过来
                return NumberUtil.compare(o2.getValue().doubleValue(), o1.getValue().doubleValue());
            });
            String collect = entryList.subList(0, 3).stream().map( m->
                    // 如现实型R，只保留R
                    StringUtils.substring(m.getKey(), m.getKey().length() - 1, m.getKey().length())
            ).collect(Collectors.joining(""));
            return new AviatorString(collect);
        }
    }

    public static String subAfter(String string, int length) {
        if (StringUtils.isNotBlank(string) && string.length() > length){
            return string.substring(string.length() - length);
        }
        return string;
    }

    static class SPMFunction extends AbstractFunction {

        /**
         * IF函数实现逻辑处理
         *
         * @param env:  当前执行的上下文
         * @param arg1: if函数中的判断逻辑
         * @param arg2: arg1为true时的结果
         * @return: com.googlecode.aviator.runtime.type.AviatorObject
         */
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
            double age = FunctionUtils.getNumberValue(arg1, env).doubleValue();
            double x = FunctionUtils.getNumberValue(arg2, env).doubleValue();
            Optional<Map.Entry<Double[], List<AgeToExpression>>> ageMap = MAP.entrySet().stream().filter(f -> f.getKey()[0] <= age && age <= f.getKey()[1]).findFirst();
            if (ageMap.isPresent()) {
                Optional<AgeToExpression> first = ageMap.get().getValue().stream().filter(f -> f.getStartX() >= x && x >= f.getEndX()).findFirst();
                if (first.isPresent()) {
                    AgeToExpression ageToExpression = first.get();
                    /**
                     * tips 参考：{@link：http://www.360doc.com/content/16/0424/00/30325915_553339181.shtml}
                     *
                     * 根据两点确定直线 y = ax + b
                     * x值：原始分
                     * y值：换算分
                     * slope:斜率，即a ==> b = y-ax，b可由线上任意一点得出
                     */
                    double slope = (ageToExpression.getStartY() - ageToExpression.getEndY()) / (ageToExpression.getStartX() - ageToExpression.getEndX());
                    return AviatorDouble.valueOf(slope * x + ageToExpression.getStartY() - slope * ageToExpression.getStartX());
                }
            }
            return null;
        }

        /**
         * 返回方法名
         */
        @Override
        public String getName() {
            return "spm";
        }
    }

    static {
        MAP.put(new Double[]{5.5d, 5.5d}, Arrays.asList(
                new AgeToExpression(60, 100, 34, 95),
                new AgeToExpression(34, 95, 29, 90),
                new AgeToExpression(29, 90, 25, 75),
                new AgeToExpression(25, 75, 16, 50),
                new AgeToExpression(16, 50, 13, 25),
                new AgeToExpression(13, 25, 12, 10),
                new AgeToExpression(12, 10, 9, 5),
                new AgeToExpression(9, 5, 0, 0)));
        MAP.put(new Double[]{6.0d, 6.0d}, Arrays.asList(
                new AgeToExpression(60, 100, 36, 95),
                new AgeToExpression(36, 95, 31, 90),
                new AgeToExpression(31, 90, 25, 75),
                new AgeToExpression(25, 75, 17, 50),
                new AgeToExpression(17, 50, 13, 25),
                new AgeToExpression(13, 25, 12, 10),
                new AgeToExpression(12, 10, 9, 5),
                new AgeToExpression(9, 5, 0, 0)));
        MAP.put(new Double[]{6.5d, 6.5d}, Arrays.asList(
                new AgeToExpression(60,100, 37,95),
                new AgeToExpression(37,95, 31,90),
                new AgeToExpression(31,90, 25,75),
                new AgeToExpression(25,75, 18,50),
                new AgeToExpression(18,50, 13,25),
                new AgeToExpression(13,25, 12,10),
                new AgeToExpression(12,10, 10,5),
                new AgeToExpression(10,5, 0,0)));
        MAP.put(new Double[]{7.0d, 7.0d}, Arrays.asList(
                new AgeToExpression(60,100, 43,95),
                new AgeToExpression(43,95, 36,90),
                new AgeToExpression(36,90, 25,75),
                new AgeToExpression(25,75, 19,50),
                new AgeToExpression(19,50, 13,25),
                new AgeToExpression(13,25, 12,10),
                new AgeToExpression(12,10, 10,5),
                new AgeToExpression(10,5, 0,0)));
        MAP.put(new Double[]{7.5d, 7.5d}, Arrays.asList(
                new AgeToExpression(60,100, 44,95),
                new AgeToExpression(44,95, 38,90),
                new AgeToExpression(38,90, 31,75),
                new AgeToExpression(31,75, 21,50),
                new AgeToExpression(21,50, 13,25),
                new AgeToExpression(13,25, 12,10),
                new AgeToExpression(12,10, 10,5),
                new AgeToExpression(10,5, 0,0)));
        MAP.put(new Double[]{8.0d, 8.0d}, Arrays.asList(
                new AgeToExpression(60,100, 44,95),
                new AgeToExpression(44,95, 39,90),
                new AgeToExpression(39,90, 31,75),
                new AgeToExpression(31,75, 23,50),
                new AgeToExpression(23,50, 15,25),
                new AgeToExpression(15,25, 13,10),
                new AgeToExpression(13,10, 10,5),
                new AgeToExpression(10,5, 0,0)));
        MAP.put(new Double[]{8.5d, 8.5d}, Arrays.asList(
                new AgeToExpression(60,100, 45,95),
                new AgeToExpression(45,95, 40,90),
                new AgeToExpression(40,90, 33,75),
                new AgeToExpression(33,75, 29,50),
                new AgeToExpression(29,50, 20,25),
                new AgeToExpression(20,25, 14,10),
                new AgeToExpression(14,10, 12,5),
                new AgeToExpression(12,5, 0,0)));
        MAP.put(new Double[]{9.0d, 9.0d}, Arrays.asList(
                new AgeToExpression(60,100, 47,95),
                new AgeToExpression(47,95, 43,90),
                new AgeToExpression(43,90, 37,75),
                new AgeToExpression(37,75, 33,50),
                new AgeToExpression(33,50, 25,25),
                new AgeToExpression(25,25, 14,10),
                new AgeToExpression(14,10, 12,5),
                new AgeToExpression(12,5, 0,0)));
        MAP.put(new Double[]{9.5d, 9.5d}, Arrays.asList(
                new AgeToExpression(60,100, 50,95),
                new AgeToExpression(50,95, 47,90),
                new AgeToExpression(47,90, 39,75),
                new AgeToExpression(39,75, 35,50),
                new AgeToExpression(35,50, 27,25),
                new AgeToExpression(27,25, 17,10),
                new AgeToExpression(17,10, 13,5),
                new AgeToExpression(13,5, 0,0)));
        MAP.put(new Double[]{10.0d, 10.0d}, Arrays.asList(
                new AgeToExpression(60,100, 50,95),
                new AgeToExpression(50,95, 48,90),
                new AgeToExpression(48,90, 42,75),
                new AgeToExpression(42,75, 35,50),
                new AgeToExpression(35,50, 27,25),
                new AgeToExpression(27,25, 17,10),
                new AgeToExpression(17,10, 13,5),
                new AgeToExpression(13,5, 0,0)));
        MAP.put(new Double[]{10.5d, 10.5d}, Arrays.asList(
                new AgeToExpression(60,100, 50,95),
                new AgeToExpression(50,95, 49,90),
                new AgeToExpression(49,90, 42,75),
                new AgeToExpression(42,75, 39,50),
                new AgeToExpression(39,50, 32,25),
                new AgeToExpression(32,25, 25,10),
                new AgeToExpression(25,10, 18,5),
                new AgeToExpression(18,5, 0,0)));
        MAP.put(new Double[]{11.0d, 11.0d}, Arrays.asList(
                new AgeToExpression(60,100, 52,95),
                new AgeToExpression(52,95, 50,90),
                new AgeToExpression(50,90, 43,75),
                new AgeToExpression(43,75, 39,50),
                new AgeToExpression(39,50, 33,25),
                new AgeToExpression(33,25, 25,10),
                new AgeToExpression(25,10, 19,5),
                new AgeToExpression(19,5, 0,0)));
        MAP.put(new Double[]{11.5d, 11.5d}, Arrays.asList(
                new AgeToExpression(60,100, 53,95),
                new AgeToExpression(53,95, 50,90),
                new AgeToExpression(50,90, 45,75),
                new AgeToExpression(45,75, 42,50),
                new AgeToExpression(42,50, 35,25),
                new AgeToExpression(35,25, 25,10),
                new AgeToExpression(25,10, 19,5),
                new AgeToExpression(19,5, 0,0)));
        MAP.put(new Double[]{12.0d, 12.0d}, Arrays.asList(
                new AgeToExpression(60,100, 53,95),
                new AgeToExpression(53,95, 50,90),
                new AgeToExpression(50,90, 46,75),
                new AgeToExpression(46,75, 42,50),
                new AgeToExpression(42,50, 37,25),
                new AgeToExpression(37,25, 27,10),
                new AgeToExpression(27,10, 21,5),
                new AgeToExpression(21,5, 0,0)));
        MAP.put(new Double[]{12.5d, 12.5d}, Arrays.asList(
                new AgeToExpression(60,100, 53,95),
                new AgeToExpression(53,95, 52,90),
                new AgeToExpression(52,90, 50,75),
                new AgeToExpression(50,75, 45,50),
                new AgeToExpression(45,50, 40,25),
                new AgeToExpression(40,25, 33,10),
                new AgeToExpression(33,10, 28,5),
                new AgeToExpression(28,5, 0,0)));
        MAP.put(new Double[]{13.0d, 13.0d}, Arrays.asList(
                new AgeToExpression(60,100, 53,95),
                new AgeToExpression(53,95, 52,90),
                new AgeToExpression(52,90, 50,75),
                new AgeToExpression(50,75, 45,50),
                new AgeToExpression(45,50, 40,25),
                new AgeToExpression(40,25, 35,10),
                new AgeToExpression(35,10, 30,5),
                new AgeToExpression(30,5, 0,0)));
        MAP.put(new Double[]{13.5d, 13.5d}, Arrays.asList(
                new AgeToExpression(60,100, 54,95),
                new AgeToExpression(54,95, 52,90),
                new AgeToExpression(52,90, 50,75),
                new AgeToExpression(50,75, 46,50),
                new AgeToExpression(46,50, 42,25),
                new AgeToExpression(42,25, 35,10),
                new AgeToExpression(35,10, 32,5),
                new AgeToExpression(32,5, 0,0)));
        MAP.put(new Double[]{14.0d, 14.0d}, Arrays.asList(
                new AgeToExpression(60,100, 55,95),
                new AgeToExpression(55,95, 52,90),
                new AgeToExpression(52,90, 50,75),
                new AgeToExpression(50,75, 48,50),
                new AgeToExpression(48,50, 43,25),
                new AgeToExpression(43,25, 36,10),
                new AgeToExpression(36,10, 34,5),
                new AgeToExpression(34,5, 0,0)));
        MAP.put(new Double[]{14.5d, 14.5d}, Arrays.asList(
                new AgeToExpression(60,100, 55,95),
                new AgeToExpression(55,95, 53,90),
                new AgeToExpression(53,90, 51,75),
                new AgeToExpression(51,75, 48,50),
                new AgeToExpression(48,50, 43,25),
                new AgeToExpression(43,25, 36,10),
                new AgeToExpression(36,10, 34,5),
                new AgeToExpression(34,5, 0,0)));
        MAP.put(new Double[]{15.0d, 15.0d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95, 54,90),
                new AgeToExpression(54,90, 51,75),
                new AgeToExpression(51,75, 48,50),
                new AgeToExpression(48,50, 43,25),
                new AgeToExpression(43,25, 36,10),
                new AgeToExpression(36,10, 34,5),
                new AgeToExpression(34,5, 0,0)));
        MAP.put(new Double[]{15.5d, 15.5d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95, 55,90),
                new AgeToExpression(55,90, 52,75),
                new AgeToExpression(52,75, 49,50),
                new AgeToExpression(49,50, 43,25),
                new AgeToExpression(43,25, 41,10),
                new AgeToExpression(41,10, 34,5),
                new AgeToExpression(34,5, 0,0)));
        MAP.put(new Double[]{16.0d, 16.0d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95, 56,90),
                new AgeToExpression(56,90, 53,75),
                new AgeToExpression(53,75, 49,50),
                new AgeToExpression(49,50, 44,25),
                new AgeToExpression(44,25, 41,10),
                new AgeToExpression(41,10, 36,5),
                new AgeToExpression(36,5, 0,0)));
        MAP.put(new Double[]{16.5d, 16.5d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95, 56,90),
                new AgeToExpression(56,90, 53,75),
                new AgeToExpression(53,75, 49,50),
                new AgeToExpression(49,50, 45,25),
                new AgeToExpression(45,25, 41,10),
                new AgeToExpression(41,10, 37,5),
                new AgeToExpression(37,5, 0,0)));
        MAP.put(new Double[]{17.0d, 19.0d}, Arrays.asList(
                new AgeToExpression(60,100, 58,95),
                new AgeToExpression(58,95, 57,90),
                new AgeToExpression(57,90, 55,75),
                new AgeToExpression(55,75, 52,50),
                new AgeToExpression(52,50, 47,25),
                new AgeToExpression(47,25, 40,10),
                new AgeToExpression(40,10, 37,5),
                new AgeToExpression(37,5, 0,0)));
        MAP.put(new Double[]{20.0d, 29.0d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95, 56,90),
                new AgeToExpression(56,90, 54,75),
                new AgeToExpression(54,75, 50,50),
                new AgeToExpression(50,50, 44,25),
                new AgeToExpression(44,25, 38,10),
                new AgeToExpression(38,10, 33,5),
                new AgeToExpression(33,5, 0,0)));
        MAP.put(new Double[]{30.0d, 39.0d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95,  55,90),
                new AgeToExpression(55,90,  52,75),
                new AgeToExpression(52,75,  48,50),
                new AgeToExpression(48,50,  43,25),
                new AgeToExpression(43,25,  37,10),
                new AgeToExpression(37,10,  28,5),
                new AgeToExpression(28,5,   0,0)));
        MAP.put(new Double[]{40.0d, 49.0d}, Arrays.asList(
                new AgeToExpression(60,100, 57,95),
                new AgeToExpression(57,95,  54,90),
                new AgeToExpression(54,90,  50,75),
                new AgeToExpression(50,75,  47,50),
                new AgeToExpression(47,50,  41,25),
                new AgeToExpression(41,25,  31,10),
                new AgeToExpression(31,10,  28,5),
                new AgeToExpression(28,5,   0,0)));
        MAP.put(new Double[]{50.0d, 59.0d}, Arrays.asList(
                new AgeToExpression(60,100, 54,95),
                new AgeToExpression(54,95,  52,90),
                new AgeToExpression(52,90,  48,75),
                new AgeToExpression(48,75,  42,50),
                new AgeToExpression(42,50,  34,25),
                new AgeToExpression(34,25,  24,10),
                new AgeToExpression(24,10,  21,5),
                new AgeToExpression(21,5,   0,0)));
        MAP.put(new Double[]{60.0d, 69.0d}, Arrays.asList(
                new AgeToExpression(60,100, 54,95),
                new AgeToExpression(54,95,  52,90),
                new AgeToExpression(52,90,  46,75),
                new AgeToExpression(46,75,  37,50),
                new AgeToExpression(37,50,  30,25),
                new AgeToExpression(30,25,  22,10),
                new AgeToExpression(22,10,  19,5),
                new AgeToExpression(19,5,   0,0)));
        MAP.put(new Double[]{70.0d, 999.0d}, Arrays.asList(
                new AgeToExpression(60,100, 52,95),
                new AgeToExpression(52,95,  49,90),
                new AgeToExpression(49,90,  44,75),
                new AgeToExpression(44,75,  33,50),
                new AgeToExpression(33,50,  26,25),
                new AgeToExpression(26,25,  18,10),
                new AgeToExpression(18,10,  17,5),
                new AgeToExpression(17,5,   0,0)));
    }

    static class AgeToExpression{

        private double startX;

        private double startY;

        private double endX;

        private double endY;

        public AgeToExpression(double startX, double startY, double endX, double endY) {
            this.startX = startX;
            this.startY = startY;
            this.endX = endX;
            this.endY = endY;
        }

        public double getStartX() {
            return startX;
        }

        public double getStartY() {
            return startY;
        }

        public double getEndX() {
            return endX;
        }

        public double getEndY() {
            return endY;
        }
    }
}
